package com.boot.aop.aspect;



import com.boot.aop.RepeatSubmitKeyRuleEnum;
import com.boot.aop.annotation.ApiRepeatSubmit;
import com.boot.config.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;

/**
 * 防止表单重复提交处理
 *
 * @author mkq
 * @data 2023/11/12 11:33:20
 */
@Slf4j
@Aspect
@Component
public class ApiRepeatSubmitAspect {

    @Autowired
    private RedisLock redisLock;


    // 通过Spring中类 LocalVariableTableParameterNameDiscoverer 获取方法参数名称
    private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

    /**
     * KEY的拼接符
     **/
    public static final String Link_Key_Tag = "\\+";

    /**
     * KEY的前缀
     **/
    public static final String Key_Prefix_Tag = "#";

    // 配置织入点
    @Pointcut("@annotation(com.boot.aop.annotation.ApiRepeatSubmit)")
    public void noRepeatSubmitPointCut() {
    }

    /**
     * 环绕通知
     */
    @Around("noRepeatSubmitPointCut()")
    public Object around(ProceedingJoinPoint point) throws Exception {
        return handleNoRepeatSubmit(point);
    }


    /**
     * 异常返回通知After throwing advice：在连接点抛出异常后执行
     */
    @AfterThrowing(value = "noRepeatSubmitPointCut()", throwing = "exception")
    public void afterThrowing(Throwable exception) {

        System.out.println("---------------@AfterThrowing----------------");
    }


    protected Object handleNoRepeatSubmit(ProceedingJoinPoint joinPoint) {
        Object proceed = null;
        // 获得注解
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        ApiRepeatSubmit annotation = method.getAnnotation(ApiRepeatSubmit.class);
        String className = method.getDeclaringClass().getName();

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

//        String token = request.getHeader("token");
        String key = this.createKey(joinPoint, annotation, request);
        // 组装key

        Boolean success = null;
        // 锁的时间
        long lockTime = annotation.lockTime();

        String redisValGuid = UUID.randomUUID().toString().replace("-", "");
        success = redisLock.lock(key, redisValGuid, lockTime);
        try {

            if (success) {
                log.info(Thread.currentThread().getName() + " 加锁成功");
                // 放行方法执行
               proceed = joinPoint.proceed();
            } else {
                log.info(Thread.currentThread().getName() + " 加锁失败");
                throw new RuntimeException(annotation.alertMsg()); // 此处可以用return 返回错误 需要跟切的方法的返回值保持一致
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            if (success) {
                boolean unlock = redisLock.unlock(key, redisValGuid);
                log.info(Thread.currentThread().getName() + (unlock ? " 解锁成功" : "解锁失败"));
            }
        }
        return proceed;
    }

    /**
     * 是否存在注解，如果存在就获取
     */
//    private NoRepeatSubmit getAnnotation(ProceedingJoinPoint joinPoint) {
//        Signature signature = joinPoint.getSignature();
//        MethodSignature methodSignature = (MethodSignature) signature;
//        Method method = methodSignature.getMethod();
//
//        if (method != null) {
//            return method.getAnnotation(NoRepeatSubmit.class);
//        }
//        return null;
//    }
    private String createKey(ProceedingJoinPoint joinPoint, ApiRepeatSubmit annotation, HttpServletRequest request) {
        String key = null;
        String requestURI = request.getRequestURI();
//        String requestMethod = request.getMethod();
//        String remoteAddr = request.getRemoteAddr();


        String userId = "";
        RepeatSubmitKeyRuleEnum keyRuleEnum = annotation.keyRule();
        if (RepeatSubmitKeyRuleEnum.TOKEN_PATH.equals(keyRuleEnum)) {
            key = request.getHeader("token") + "." + requestURI;
        } else if (RepeatSubmitKeyRuleEnum.USER_PATH.equals(keyRuleEnum)) {
            key = userId + "." + requestURI;
        } else if (RepeatSubmitKeyRuleEnum.USER_PATH_KEY.equals(keyRuleEnum)) {
            key = userId + "." + requestURI + "." + this.getKeyVal(joinPoint, annotation, request);
        } else if (RepeatSubmitKeyRuleEnum.PATH_KEY.equals(keyRuleEnum)) {
            key = requestURI + "." + this.getKeyVal(joinPoint, annotation, request);
        } else if (RepeatSubmitKeyRuleEnum.KEY.equals(keyRuleEnum)) {
            key = this.getKeyVal(joinPoint, annotation, request);
        }

        return key;
    }

    private String getKeyVal(ProceedingJoinPoint joinPoint, ApiRepeatSubmit annotation, HttpServletRequest request) {

        String keys = annotation.keys();
        if (StringUtils.isBlank(keys)) {
            return null;
        }
        StringBuffer keysVal = new StringBuffer("");
        // 获取get请求参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        // 获取路径参数：使用@PathVariable注解传入的参数
        Map mapPath = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        for (String key : keys.split(Link_Key_Tag)) {
            String val = null;
            if (key.contains(".")) {
                val = getEntityField(joinPoint, key);
            } else {
                String key_ = key.replace(Key_Prefix_Tag, "");
                if (parameterMap.size() > 0 && parameterMap.containsKey(key_)) {
                    val = parameterMap.get(key_)[0];
                }
                if (mapPath.size() > 0 && mapPath.containsKey(key_)) {
                    val = mapPath.get(key_) + "";

                }
            }
            if (StringUtils.isNotBlank(val)) {
                keysVal.append(val).append("+");
            }

        }

        return keysVal.toString().substring(0, keysVal.toString().length() - 1);
    }


    // 如果key是对象属性
    private String getEntityField(ProceedingJoinPoint joinPoint, String key) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String[] params = discoverer.getParameterNames(method);
        Object[] args = joinPoint.getArgs();
        //将参数名与参数值对应起来
        EvaluationContext context = new StandardEvaluationContext();
        for (int len = 0; len < params.length; len++) {
            Object arg = args[len];
            context.setVariable(params[len], args[len]);
        }
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(key);
        Object expressionValue = expression.getValue(context, Object.class);
        return expressionValue == null ? null : expressionValue.toString();
    }

}
